/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package ru.ssau.presenter;

import ru.ssau.lib.Request;
import ru.ssau.lib.AppendableObjectOutputStream;
import ru.ssau.lib.AppendableObjectInputStream;
import java.io.*;
import java.net.*;
import java.util.concurrent.ArrayBlockingQueue;
import org.jdom.Element;
import ru.ssau.entities.Message;
import ru.ssau.entities.User;
import ru.ssau.lib.Response;
import ru.ssau.utils.XMLHelper;
import ru.ssau.view.ForumView;

/**
 *
 * @author Дима
 */
public class ClientPresenter {

    private String hostName;
    private int port;
    private Socket socket;
    private AppendableObjectInputStream in;
    private AppendableObjectOutputStream out;
    private ForumView view;
    private ArrayBlockingQueue<Boolean> flagStore;
    private ArrayBlockingQueue<ru.ssau.entities.Thread> threadStore;
    private ArrayBlockingQueue<Message> messageStore;
    private ArrayBlockingQueue<User> userStore;

    public ClientPresenter(String hostName, int port, ForumView view) throws ConnectException {
        this.hostName = hostName;
        this.port = port;
        establishConnection();
        this.view = view;

        flagStore = new ArrayBlockingQueue<Boolean>(1);
        userStore = new ArrayBlockingQueue<User>(1);
        messageStore = new ArrayBlockingQueue<Message>(1);
        threadStore = new ArrayBlockingQueue<ru.ssau.entities.Thread>(1);
    }

    /**
     * Здесь происходит обработка всего, что присылает сервер
     */
    public void service() {
        try {
            //Получение состояния модели на момент подключения
            sendModelStateRequest();
            while (socket.isConnected()) { 
                Response response = (Response) receiveFromServer();
                if(response.getAction() == Response.CHANGE) {
                    flagStore.add(response.getCanChangeModel());
                }

                else if(response.getAction() == Response.ADD) {
                    if (response.getEntityType() == Response.MESSAGE) {
                        view.addToMessageList(response.getMessageListElement());
                    } else if(response.getEntityType() == Response.USER) {
                        view.addToUserList(response.getUserListElement());
                    } else if(response.getEntityType() == Response.THREAD) {
                        view.addToThreadList(response.getThreadListElement());
                    }
                }

                else if(response.getAction() == Response.GET) {
                    if(response.getEntityType() == Response.MESSAGE) {
                        messageStore.add(response.getMessage());
                    } else if(response.getEntityType() == Response.USER) {
                        userStore.add(response.getUser());
                    } else if(response.getEntityType() == Response.THREAD) {
                        threadStore.add(response.getThread());
                    }
                }

                else if(response.getAction() == Response.SET) {
                    if(response.getEntityType() == Response.MESSAGE) {
                        view.setCurrentMessage(response.getMessage());
                    } else if(response.getEntityType() == Response.USER) {
                        view.setCurrentUser(response.getUser());
                    } else if(response.getEntityType() == Response.THREAD) {
                        view.setCurrentThread(response.getThread());
                    }
                }

                else if(response.getAction() == Response.DELETE) {
                    if(response.getEntityType() == Response.MESSAGE) {
                        view.setMessageList(response.getMessages());
                    } else if(response.getEntityType() == Response.USER) {
                        view.setUserList(response.getUsers());
                        view.setMessageList(response.getMessages());
                    } else if(response.getEntityType() == Response.THREAD) {
                        view.setThreadList(response.getThreads());
                        view.setMessageList(response.getMessages());
                    }
                }

                else if(response.getAction() == Response.REPLACE) {
                    if(response.getEntityType() == Response.MESSAGE) {
                        view.replaceMessage(response.getMessageListElement(), response.getMessages());
                    } else if(response.getEntityType() == Response.THREAD) {
                        view.replaceThread(response.getThreadListElement(), response.getThreads());
                    } else if(response.getEntityType() == Response.USER) {
                        view.replaceUser(response.getUserListElement(), response.getUsers());
                    }
                }

                else if(response.getAction() == Response.STATE_NOTIFICATION) {
                    view.setMessageList(response.getMessages());
                    view.setThreadList(response.getThreads());
                    view.setUserList(response.getUsers());
                }
            }
        } catch (IOException e) {
            view.showConnectionClosedDialog();
            e.printStackTrace();
        } 
    }

    /**
     * Показывает, может ли пользователь изменять модель.
     * @return <code>true</code>, если пользователь заблокировал модель и может ее изменять;
     * <code>false</code>, если модель заблокирована другим пользователем.
     */
    public Boolean getCanChangeModel() throws IOException, InterruptedException {
        Request action = new Request(Request.CHANGE);
        sendToServer(action);
        Boolean flag = flagStore.take();
        return flag;
    }

    /**
     * Посылает запрос на предоставление моделью ее текущего состояния.
     * @throws IOException
     */
    private void sendModelStateRequest() throws IOException {
        Request action = new Request(Request.STATE_NOTIFICATION);
        sendToServer(action);
    }


    /*Thread section*/
    public void addThread(ru.ssau.entities.Thread thread) throws IOException {
        Request action = new Request(Request.THREAD, Request.ADD, thread);
        sendToServer(action);
    }

    public void replaceThread(ru.ssau.entities.Thread thread) throws IOException {
        Request action = new Request(Request.THREAD, Request.REPLACE, thread);
        sendToServer(action);
    }

    public void setCurrentThread(int id) throws IOException {
        Request action = new Request(Request.THREAD, Request.SET, id);
        sendToServer(action);
    }

    public ru.ssau.entities.Thread getThreadById(int id) throws IOException, InterruptedException {
        Request action = new Request(Request.THREAD, Request.GET, id);
        sendToServer(action);
        ru.ssau.entities.Thread thread = threadStore.take();
        return thread;
    }

    public void deleteThreadById(int id) throws IOException {
        Request action = new Request(Request.THREAD, Request.DELETE, id);
        sendToServer(action);
    }

    public void editThread() throws IOException {
        Request action = new Request(Request.THREAD, Request.CHANGE);
        sendToServer(action);
    }


    /*Message section*/
    public void addMessage(Message message) throws IOException {
        Request action = new Request(Request.MESSAGE, Request.ADD, message);
        sendToServer(action);
    }

    public void replaceMessage(Message message) throws IOException {
        Request action = new Request(Request.MESSAGE, Request.REPLACE, message);
        sendToServer(action);
    }

    public void setCurrentMessage(int id) throws IOException {
        Request action = new Request(Request.MESSAGE, Request.SET, id);
        sendToServer(action);
    }
    
    public Message getMessageById(int id) throws IOException, InterruptedException {
        Request action = new Request(Request.MESSAGE, Request.GET, id);
        sendToServer(action);
        Message message = messageStore.take();
        return message;
    }

    public void deleteMessageById(int id) throws IOException {
        Request action = new Request(Request.MESSAGE, Request.DELETE, id);
        sendToServer(action);
    }

    public void editMessage() throws IOException {
        Request action = new Request(Request.MESSAGE, Request.CHANGE);
        sendToServer(action);
    }


    /*User section*/
    public void addUser(User user) throws IOException {
        Request action = new Request(Request.USER, Request.ADD, user);
        sendToServer(action);
    }

    public void replaceUser(User user) throws IOException {
        Request action = new Request(Request.USER, Request.REPLACE, user);
        sendToServer(action);
    }

    public void setCurrentUser(int id) throws IOException {
        Request action = new Request(Request.USER, Request.SET, id);
        sendToServer(action);
    }

    public User getUserById(int id) throws IOException, InterruptedException {
        Request action = new Request(Request.USER, Request.GET, id);
        sendToServer(action);
        User user = userStore.take();
        return user;
    }

    public void deleteUserById(int id) throws IOException {
        Request action = new Request(Request.USER, Request.DELETE, id);
        sendToServer(action);
    }

    public void editUser() throws IOException {
        Request action = new Request(Request.USER, Request.CHANGE);
        sendToServer(action);
    }


    /*General section */
    private void sendToServer(Request action) throws IOException {    
//        out.writeObject(action);
        out.writeUTF(XMLHelper.outputXML(action.toXMLElement()));
        out.flush();
    }

    private Object receiveFromServer() throws IOException {
        /*try {
            return in.readObject();*/
            Response response=new Response();
            Element root=XMLHelper.parseString(in.readUTF());
            response.loadFromXMLElement(root);
            return response;
        /*} catch (ClassNotFoundException e) {
            throw new IOException("Cannot receive an object from server", e);
        }*/
    }

    public void establishConnection() throws ConnectException {
        int timeOut = 10000;
        try {
            if (socket == null) {
                socket = new Socket();
            }
            socket.connect(new InetSocketAddress(hostName, port), timeOut);
            in = new AppendableObjectInputStream(socket.getInputStream());
            out = new AppendableObjectOutputStream(socket.getOutputStream());
        } catch (IOException e) {
            ConnectException connEx = new ConnectException("Cannot "
                    + "establish a connection to '"
                    + hostName + "' on port " + port);
            throw connEx;
        }
    }

    public void closeConnection() throws IOException {
        if (in != null) {
            in.close();
        }
        if (out != null) {
            out.close();
        }
        if (socket != null) {
            socket.close();
        }
    }
}
